package com.boot2.core.conf.mqtt;

import com.boot2.core.mqtt.MqttProperties;
import com.boot2.core.utils.StringUtils;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;

/**
 * @author zhangweilin
 * @date 2018/9/20 11:32
 * @description: 基层mqtt配置
 */
@CommonsLog
public class MqttConf {

    public MqttPahoClientFactory mqttClientFactory(final MqttProperties mqttProperties,boolean cleanSession) throws Exception {
        String[] serverURIArr = mqttProperties.getUrl().split(",");
        String userName = mqttProperties.getUsername();
        String password = mqttProperties.getPassword();

        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
        // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录，这里设置为true表示每次连接到服务器都以新的身份连接
        mqttConnectOptions.setCleanSession(cleanSession);
        //设置了用户名密码才启用
        if (StringUtils.isNotEmpty(userName) && StringUtils.isNotEmpty(password)) {
            // 设置连接的用户名
            mqttConnectOptions.setUserName(userName);
            // 设置连接的密码
            mqttConnectOptions.setPassword(password.toCharArray());
        }
//        SSLSocketFactory sslSocketFactory=getSSLSocktet("youpath/ca.crt","youpath/client.crt","youpath/client.pem","password");
        //只有手动开启了ssl才使用ssl证书
        if (mqttProperties.getIsOpenssl()) {
            log.info("使用ssl连接mqtt");
            //兼容linux,主要是pcks1和pcks8的兼容
            //https://stackoverflow.com/questions/6559272/algid-parse-error-not-a-sequence
            java.security.Security.addProvider(
                    new org.bouncycastle.jce.provider.BouncyCastleProvider()
            );
            SSLSocketFactory sslSocketFactory = getSSLSocktet(mqttProperties);
            mqttConnectOptions.setSocketFactory(sslSocketFactory);
        }
        // 设置超时时间 单位为秒
        if (null!=mqttProperties.getConnectionTimeout()) {
            mqttConnectOptions.setConnectionTimeout(mqttProperties.getConnectionTimeout());
        }
        // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线，但这个方法并没有重连的机制
        if (null!=mqttProperties.getKeepAliveInterval()) {
            mqttConnectOptions.setKeepAliveInterval(mqttProperties.getKeepAliveInterval());
        }
        mqttConnectOptions.setServerURIs(serverURIArr);
        factory.setConnectionOptions(mqttConnectOptions);


//        IMqttClient client = null;
//        try {
//            client = factory.getClientInstance(serverURIs, clientId);
//        } catch (MqttException e) {
//            e.printStackTrace();
//        }
//        MqttTopic topic = client.getTopic(inboundTopics);
//        // setWill方法，如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
//        mqttConnectOptions.setWill(topic, "close".getBytes(), 2, true);
//         factory.setUserName("username");
//         factory.setPassword("password");
        return factory;
    }


    private SSLSocketFactory getSSLSocktet(MqttProperties mqttProperties) throws Exception {
        // CAcertificate is used to authenticate server
        String sslCaPath = mqttProperties.getSslCaPath();
        String sslCrtPath = mqttProperties.getSslCrtPath();
        String sslKeyPath = mqttProperties.getSslKeyPath();
        String sslProtocol = mqttProperties.getSslProtocol();
        log.info("sslCaPath: "+sslCaPath);
        log.info("sslCrtPath: "+sslCrtPath);
        log.info("sslKeyPath: "+sslKeyPath);
        log.info("sslProtocol: "+sslProtocol);

        CertificateFactory cAf = CertificateFactory.getInstance("X.509");
        FileInputStream caIn = new FileInputStream(sslCaPath);
        X509Certificate ca = (X509Certificate) cAf.generateCertificate(caIn);
        KeyStore caKs = KeyStore.getInstance("JKS");
        caKs.load(null, null);
        caKs.setCertificateEntry("ca-certificate", ca);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
        tmf.init(caKs);

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        FileInputStream crtIn = new FileInputStream(sslCrtPath);
        X509Certificate caCert = (X509Certificate) cf.generateCertificate(crtIn);

        crtIn.close();
        // clientkey and certificatesare sent to server so it can authenticate
        // us
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
//      ks.load(caIn,password.toCharArray());
        ks.load(null, null);
        ks.setCertificateEntry("certificate", caCert);

        ks.setKeyEntry("private-key", getPrivateKey(sslKeyPath), mqttProperties.getSslPassword().toCharArray(),
                new java.security.cert.Certificate[]{caCert});
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
        kmf.init(ks, mqttProperties.getSslPassword().toCharArray());
//      keyIn.close();

        // finally,create SSL socket factory

        SSLContext context = SSLContext.getInstance(sslProtocol);
        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
        return context.getSocketFactory();
    }


    public PrivateKey getPrivateKey(String path) throws Exception {

        org.apache.commons.codec.binary.Base64 base64 = new Base64();
        byte[] buffer = base64.decode(getPem(path));

        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    }

    private String getPem(String path) throws Exception {
        FileInputStream fin = new FileInputStream(path);
        BufferedReader br = new BufferedReader(new InputStreamReader(fin));
        String readLine = null;
        StringBuilder sb = new StringBuilder();
        while ((readLine = br.readLine()) != null) {
            if (readLine.charAt(0) == '-') {
                continue;
            } else {
                sb.append(readLine);
                sb.append('\r');
            }
        }
        fin.close();
        return sb.toString();
    }
}
